/**
 * @author Flavio Codeco Coelho
 * copyright 2008-2009 by the author
 * License GPL v3
 */

var ctx
var graph
var drawInterval
var startX
var startY
var repelLimit = 80 // need to be most repelled by home
var timeTilDraw = 50
var distanceToSense = 60

// From Benjamin Joffe, http://www.random.abrahamjoffe.com.au/public/JavaScripts/canvas/fonts.htm
var tahoma8=new Image();
tahoma8.src='';
tahoma8.c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!@#$%^&*()-=[]\\;\',./_+{}|:"<>?`~';
tahoma8.w=[6,6,5,6,6,4,6,6,2,3,5,2,8,6,6,6,6,4,5,4,6,6,8,6,6,5,7,6,7,7,6,6,7,7,4,5,6,5,8,7,8,6,8,7,6,6,7,6,10,6,6,6,3,6,6,6,6,6,6,6,6,6,6,4,10,8,6,11,8,7,6,4,4,4,8,4,4,4,4,2,4,4,4,6,8,5,5,4,4,4,8,8,5,6,8];
tahoma8.h=13;

var tahoma8bold=new Image();
tahoma8bold.src='';
tahoma8bold.c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!@#$%^&*()-=[]\\;\',./_+{}|:"<>?`~';
tahoma8bold.w=[7,7,6,7,7,4,7,7,3,4,7,3,11,7,7,7,7,5,6,5,7,7,9,7,7,6,8,7,7,8,6,6,8,8,5,6,7,6,10,7,8,7,8,8,7,7,8,7,11,7,7,7,3,7,7,7,7,7,7,7,7,7,7,3,10,9,7,13,9,9,7,5,5,5,9,5,5,6,3,3,3,3,6,7,9,7,7,7,3,6,9,9,6,6,10];
tahoma8bold.h=15;

CanvasRenderingContext2D.prototype.drawString=function(s, f, x, y){
    y=Math.round(y);
    var z=x=Math.round(x),t,i,j;
    if(!f.f){
        f.f=[t=0],i=0,j=f.w.length;
        while(++i<j)f.f[i]=t+=f.w[i-1];
    }
    s=s.split(''),i=0,j=s.length;
    while(i<j)if((t=f.c.indexOf(s[i++]))>=0)
        this.drawImage(f,f.f[t],0,f.w[t],f.height,x,y,f.w[t],f.height),x+=f.w[t];
    else if(s[i-1]=='\n')x=z,y+=f.h;
}
// End Joffe: Thanks Benjamin!

function Graph(context){
    // Graph class
    this.nodes = []
    this.edges = []
    this.ctx = context
    this.canvas = document.getElementById("canvas")
    this.toString = function(){
        return "Nodes: "+this.nodes+"\n"+ "Edges: "+this.edges.lenght
    }
    this.addNode = function(node){
        //Add a single node
        //node is an object like this {name]:'teste',x:50,y:50,size:30,color:'rgba(0,255,100,1)'}
        this.nodes[this.nodes.length]=node;
    }
    this.addEdge = function(edge){
        //Add a single edges
        //edge is an object like this {source:{...},destination:{...},width:3,on:true}
        edge.source.addEdge(edge.destination,500)
        edge.destination.addEdge(edge.source,500)
        this.edges.push(edge);
    }
    this.addNodesFrom = function(nodelist){
        //Add multiple nodes.
        //nodelist is an array of node objects
        for (var n in nodelist){
            this.addNode(nodelist[n])
        }
    }
    this.addEdgesFrom = function(edgelist){
        //Add multiple edges.
        //edgelist is an array of edge objects
        for (var e in edgelist){
            this.addEdge(edgelist[e])
        }
    }
    this.render = function(){
        //Renders the graph
		
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
        for (var e in this.edges){
            this.edges[e].render(this.ctx);
        }
        for (var n in this.nodes){
            this.nodes[n].render(this.ctx);
        }
    }
    // Interaction related methods
    this.userMovedTo = function(position) {
        //ctx.drawString('over',tahoma8,position.x+2,position.y)
        //this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
        for ( var i = 0; i < this.nodes.length; i++){
            //ctx.drawString(i.toString(),tahoma8,position.x+15,position.y)
            var node = this.nodes[i]
            node.onOver(position)
            node.render(this.ctx)
        //node.renderEdges(this.ctx)
        }
    }
    this.isInBounds = function(position) {
        return (position.x < this.canvas.width && position.x>0) && (position.y < this.canvas.height && position.y >0)
    }
    this.userClicked = function(position) {
        if (this.isInBounds(position)) {
            for (var i = 0; i < this.nodes.length; i++) {
                var node = this.nodes[i]
                if (node.intersects(position.x, position.y)) {
                    node.onDown(position)
                }
            }
        }
    }
    this.userReleased = function(position) {
        for (var i = 0; i < this.nodes.length; i++) {
            this.nodes[i].onUp(position)
        }
    }
    this.doubleClick = function(position) {
        if (this.isInBounds(position)) {
            for (var i = 0; i < this.nodes.length; i++) {
                var node = this.nodes[i]
                if (node.isClickable && node.intersects(position.x, position.y)) {
                    node.position.x = this.ctx.width/2
                    node.position.y = this.ctx.height/2
                }
            }
        }
    }
}
function Node (name,X,Y,size,color,context){
    //Node Class
    this.name = name;
    this.ctx = context
    this.position = {
        x: X,
        y: Y
    }
    this.edges = new Array()
    this.repellants = new Array()
    this.radius = size //radius of the circle
    this.color = color // rgba(123,255,0,0.7)
    this.originalFillStyle = color
    this.fillStyle = color
    this.move = 3
    this.isClickable = false
    this.render = function(ctx){
        ctx.fillStyle = this.fillStyle;
        ctx.strokeStyle = "black";
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.arc(this.position.x,this.position.y,this.radius,0,2*Math.PI,true);
        ctx.stroke();
        ctx.fill();
    }
    this.renderEdges = function(ctx){
        // Render the edges associated with this node.
        for (var e in this.edges){
            this.edges[e].render(ctx);
        }
    }
    this.toString = function(){
        return "[Node named "+this.name+", located at ("+this.position.x+","+this.position.y+")]"
    }
    this.onDown = function(position){
        // Mouse down method
        this.dragging = position
        this.fillStyle = 'yellow'
        this.render(this.ctx)
    }
    this.onUp = function(position){
        // Mouse up method
        this.dragging = undefined
        this.fillStyle = this.originalFillStyle
        //this.situate()
        this.render(this.ctx)
		
    }
    this.onOver = function (position){
        if (this.intersects(position.x, position.y)) {
            //this.rolloverContent()
            this.fillStyle = "orange"
        //ctx.drawString('over',tahoma8,position.x+15,position.y+15)
        } else {
            this.fillStyle = this.originalFillStyle
        //ctx.drawString('not over',tahoma8,position.x+15,position.y+15)
        }

        if (this.dragging != undefined) {
            deltaX = position.x - this.dragging.x
            deltaY = position.y - this.dragging.y
            this.position.x += deltaX
            this.position.y += deltaY

            this.dragging = position
        } else {
            this.situate()
        }
    }
    this.intersects = function(x,y){
        return this.intersectsWith({
            position: {
                x: x,
                y: y
            }
            })
    }
    this.intersectsWith = function(other){
        return this.distanceFrom(other) <= this.radius
    }
    this.distanceFrom = function(other){
        var x = other.position.x - this.position.x
        var y = other.position.y - this.position.y
        return Math.sqrt(x * x + y * y)
    }
    this.moveAwayFrom = function(r, multiplier){
        var move = this.move * multiplier
        if (r.position.x < this.position.x) {
            this.position.x += this.move
        } else if (r.position.x > this.position.x) {
            this.position.x -= this.move
        } else {
            var chance = Math.random()
            if (chance > .5) {
                this.position.x += this.move
            } else {
                this.position.x -= this.move
            }
        }
        if (r.position.y < this.position.y) {
            this.position.y += this.move
        } else if (r.position.y > this.position.y) {
            this.position.y -= this.move
        } else {
            var chance = Math.random()
            if (chance > .5) {
                this.position.y += this.move
            } else {
                this.position.y -= this.move
            }
        }
    }
    this.addRepellant = function(r){
        if (r == this) {
            return
        }
        for (var i = 0; i < this.repellants.length; i++) {
            if (r == this.repellants[i]) {
                return
            }
        }
        this.repellants[this.repellants.length] = r
    }
    this.addEdge = function(r, maxDistance) {
        //add edge to the node's edge array. r is the other.
        this.edges[this.edges.length] =  {
            r: r,
            d: maxDistance
        }
    }
    this.hasCollisionWith = function(other) {
        return this.distanceFrom(other) <= (this.radius + other.radius)
    }
    this.situate = function(){
        if (this.dragging != undefined) {
            return
        }

        for (var i = 0; i < this.repellants.length; i++) {
            var r = this.repellants[i]
            if (this.distanceFrom(r) < repelLimit) {
                this.moveAwayFrom(r,2)
            }
        }

        /*
for (var i = 0; i < this.edges.length; i++) {
			var r = this.edges[i].r
			this.moveAwayFrom(r,2)
		}
*/

        for (var i = 0; i < this.edges.length; i++) {
            var maxDistance = this.edges[i].d
            var r = this.edges[i].r
            while (this.distanceFrom(r) > maxDistance) {
                if (r.position.x < this.position.x) {
                    this.position.x--
                } else if (r.position.x > this.position.x) {
                    this.position.x++
                }

                if (r.position.y < this.position.y) {
                    this.position.y--
                } else if (r.position.y > this.position.y) {
                    this.position.y++
                }
            }
        }

    /*
for (var i = 0; i < this.repellants.length; i++) {
			var r = this.repellants[i]
			while (this.hasCollisionWith(r)) {
				this.moveAwayFrom(r,2)
			}
		}
*/
    }
}

function Edge (source,destination,weight,on){
    //Edge class
    this.source = source
    this.destination = destination
    this.weight = weight
    this.on = on //boolean for on off status
    this.render  = function(ctx){
        if (this.on){
            ctx.strokeStyle = 'rgba(255,0,0,0.5)';
        }
        else{
            ctx.strokeStyle = 'rgba(0,0,0,0.5)';
        }
        ctx.beginPath();
        ctx.lineWidth = this.weight;
        ctx.moveTo(source.position.x,source.position.y);
        ctx.lineTo(destination.position.x,destination.position.y);
        ctx.stroke();
    }
    this.toString = function(){
        return "[edge connecting "+this.source.name+" and "+this.destination.name+"]"
    }
}

function start(){
    //setup interactive canvas
    if (document.getElementById('canvas').getContext("2d")) {
        ctx = document.getElementById('canvas').getContext("2d")
        graph = new Graph(ctx)
        window.addEventListener("mousemove", on_mousemove, false)
        window.addEventListener("mousedown", on_mousedown, false)
        window.addEventListener("mouseup", on_mouseup, false)
        window.addEventListener("dblclick", on_doubleclick, false)
    //window.addEventListener("resize", resizeCanvas, false)
		
    }
    else {
        alert("You have to use Firefox 1.5+ or safari to see the graph")
    }
	
}

function go(){
    drawInterval = window.setInterval(draw, timeTilDraw)
}


function resizeCanvas() {
    document.getElementById('canvas').height = window.innerHeight
}
function getRelativePosition(e) {
    cnv = document.getElementById('canvas')
    return {
        x: e.clientX - cnv.offsetLeft,
        y: e.clientY - cnv.offsetTop
        };
}

function on_mousemove(e) {
    var position = getRelativePosition(e)
    graph.userMovedTo(position)
}

function on_doubleclick(e) {
    var position = getRelativePosition(e)
    graph.doubleClick(position)
}

function on_mousedown(e) {
    var position = getRelativePosition(e)
    graph.userClicked(position)
//clearInterval(drawInterval)
}

function on_mouseup(e) {
    var position = getRelativePosition(e)
    graph.userReleased(position)
    drawInterval = window.setInterval(draw, timeTilDraw)
}
function draw(){
    graph.render()
}
function setupRepellants() {
    for (var i = 0; i < graph.nodes.length; i++) {
        for (var j = 0; j < graph.nodes.length; j++) {
            if (i != j) {
                graph.nodes[i].addRepellant(graph.nodes[j])
            }
        }
    }
}

